package com.zk.bytecode.trace;


import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * 对StackTrace进行方法级记录的对象
 * 其中lineNumber是StackTrace中，调用到当前方法的上级Trace的lineNumber
 */
public class StackTraceMethod {
    public static final int NOT_EXIST = -999;
    private List<StackTraceElement> stack = new ArrayList<>();

    private String className;
    private String methodName;
    private String fileName;

    public List<StackTraceElement> stack() {
        return stack;
    }

    public void stack(List<StackTraceElement> stackTraceElements) {
        stack = new ArrayList<>(stackTraceElements);
    }

    void init(StackTraceElement stackTraceElement) {
        this.className = stackTraceElement.getClassName();
        this.methodName = stackTraceElement.getMethodName();
        this.fileName = stackTraceElement.getFileName();
    }

    // 入口方法的Stack深度
    public int depth() {
        return stack.size() + 1;
    }

    /**
     * Creates a stack trace element representing the specified execution
     * point.
     *
     * @throws NullPointerException if {@code declaringClass} or
     *                              {@code methodName} is null
     * @since 1.5
     */
    public StackTraceMethod(List<StackTraceElement> stackTrace) {
        int invokedLineNumber = stackTrace.size() > 1 ? stackTrace.get(1).getLineNumber() : StackTraceMethod.NOT_EXIST;
        init(stackTrace.get(0));
        if (invokedLineNumber != StackTraceMethod.NOT_EXIST) {
            this.stack = Collections.unmodifiableList(stackTrace.subList(1, stackTrace.size()));
        }
    }


    /**
     * Returns the name of the source file containing the execution point
     * represented by this stack trace element.  Generally, this corresponds
     * to the {@code SourceFile} attribute of the relevant {@code class}
     * file (as per <i>The Java Virtual Machine Specification</i>, Section
     * 4.7.7).  In some systems, the name may refer to some source code unit
     * other than a file, such as an entry in source repository.
     *
     * @return the name of the file containing the execution point
     * represented by this stack trace element, or {@code null} if
     * this information is unavailable.
     */
    public String getFileName() {
        return fileName;
    }

    /**
     * Returns the fully qualified name of the class containing the
     * execution point represented by this stack trace element.
     *
     * @return the fully qualified name of the {@code Class} containing
     * the execution point represented by this stack trace element.
     */
    public String getClassName() {
        return className;
    }

    /**
     * Returns the name of the method containing the execution point
     * represented by this stack trace element.  If the execution point is
     * contained in an instance or class initializer, this method will return
     * the appropriate <i>special method name</i>, {@code <init>} or
     * {@code <clinit>}, as per Section 3.9 of <i>The Java Virtual
     * Machine Specification</i>.
     *
     * @return the name of the method containing the execution point
     * represented by this stack trace element.
     */
    public String getMethodName() {
        return methodName;
    }


    /**
     * 在stack的长度内，记录的时stack 四要素相等，当长度达到 当前Method时，比较三要素。
     *
     * @param srcStack
     * @return
     */
    int equalSize(List<StackTraceElement> srcStack) {
        int minSize = Math.min(srcStack.size(), this.stack.size());
        for (int i = 0; i < minSize; i++) {
            if (!Objects.equals(srcStack.get(srcStack.size() - i - 1)
                    , this.stack.get(this.stack.size() - i - 1))) {
                return i;
            }
        }
        // 当传入Stack完全匹配时，返回minSize
        if (minSize == srcStack.size()) {
            return minSize;
        }
        // 当内部stack完全匹配时，比较当前方法Method
        // 因交集部分已经通过比较法排除，此处stackTraceElements的size()必然大于minSize
        if (minSize == this.stack.size()) {
            StackTraceElement stackTraceElement = srcStack.get(srcStack.size() - minSize - 1);
            if (this.methodEquals(stackTraceElement)) {
                return minSize + 1;
            }
        }
        return minSize;
    }

    public boolean methodEquals(StackTraceElement e) {
        if (e == null) {
            return false;
        }
        return Objects.equals(className, e.getClassName()) &&
                Objects.equals(methodName, e.getMethodName()) &&
                Objects.equals(fileName, e.getFileName());
    }


    public boolean equals(Object obj) {
        if (obj == this)
            return true;
        if (!(obj instanceof StackTraceMethod))
            return false;
        StackTraceMethod e = (StackTraceMethod) obj;
        return e.className.equals(className) &&
                Objects.equals(methodName, e.methodName) &&
                Objects.equals(fileName, e.fileName);
    }
}
